01 de marzo de 2018

Introducción a Tidyverse.

Tidyverse

Es una colección de paquetes de R diseñados para la ciencia de datos que facilitan la manipulación, exploración y visualización de datos.

Todos los paquetes comparten una filosofía de diseño, gramática y estructuras de datos.

La mayoría de los paquetes fueron originalmente diseñados por Hadley Wickham pero ahora hay muchos contribuidores.

Flujo de trabajo

Entorno accesible para el análisis de datos.

Core tidyverse

Está diseñado para facilitar la instalación y la carga de varios paquetes en un solo comando.

# install.packages("tidyverse")
library("tidyverse")
## ── Attaching packages ───────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 2.2.1     ✔ purrr   0.2.4
## ✔ tibble  1.4.2     ✔ dplyr   0.7.4
## ✔ tidyr   0.8.0     ✔ stringr 1.2.0
## ✔ readr   1.1.1     ✔ forcats 0.2.0
## ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()

Paquetes principales

Paquetes principales

Pipe

magrittr

El objetivo de usar pipes es hacer el desarrollo de código más fácil de escribir, mas rápido de leer y más sencillo de dar mantenimiento.

Operador %>%

Funciona con el operador forward pipe %>% que envía un valor a una función.

La función básica puede verse de la siguiente forma:

  • x %>% f equivale a f(x).
  • x %>% f(y) equivale a f(x, y).
  • x %>% f %>% g %>% h equivale a h(g(f(x))).

Argumentos

Colocación de valores en argumentos por posición se realiza con un punto .:

  • x %>% f(y, .) equivale a f(y, x).
  • x %>% f(y, z = .) equivale a f(y, z = x)

Shortcut

El shortcut de forward pipe `%>%` es command/ctrl + shift + m

Pipe: ejemplo sin pipe

library(nycflights13)
foo1 <- filter(flights, carrier == "UA")
foo2 <- arrange(foo1, desc(dep_time))
head(foo2, 3)
## # A tibble: 3 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     4    18     2358           2048       190      340
## 2  2013     3    24     2357           2151       126       44
## 3  2013     4    10     2357           1935       262      148
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>

Pipe: ejemplo con pipe

 flights %>% 
  filter(carrier == "UA") %>% 
  arrange(desc(dep_time)) %>% 
  head(3)
## # A tibble: 3 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     4    18     2358           2048       190      340
## 2  2013     3    24     2357           2151       126       44
## 3  2013     4    10     2357           1935       262      148
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>

Pipeline

  1. Manipular
  2. Explorar
  3. Visualizar
  4. Modelar

Manipular

El arte de tener los datos en R en una forma útil para la visualización y modelado

EJ: Importar datos

df_ejercicio <- read_csv("data/datos_ejercicio.csv")
## Parsed with column specification:
## cols(
##   ent_mun = col_character(),
##   indicador = col_character(),
##   `2015` = col_double(),
##   `2016` = col_character()
## )

EJ: Imprimir datos

df_ejercicio
## # A tibble: 24,561 x 4
##    ent_mun indicador  `2015` `2016`
##    <chr>   <chr>       <dbl> <chr> 
##  1 09014   analfabeta  0.560 <NA>  
##  2 15020   analfabeta  0.970 <NA>  
##  3 19006   analfabeta  1.11  <NA>  
##  4 09016   analfabeta  1.21  <NA>  
##  5 19046   analfabeta  1.23  <NA>  
##  6 20350   analfabeta  1.25  <NA>  
##  7 26019   analfabeta  1.29  <NA>  
##  8 26024   analfabeta  1.35  <NA>  
##  9 08019   analfabeta  1.38  <NA>  
## 10 15024   analfabeta  1.43  <NA>  
## # ... with 24,551 more rows

filter() y arrange()

# Filtrar y ordenar
df_fo <- df_ejercicio %>% 
  filter(indicador %in% c("analfabeta", "sin_primaria", "poblacion")) %>% 
  arrange(ent_mun, indicador)%>% 
  mutate(est = str_sub(ent_mun, start = 1, end = 2),
         mun = str_sub(ent_mun, start = 3, end = 5)) %>% 
  select(-ent_mun)
df_fo 
## # A tibble: 7,369 x 5
##    indicador       `2015` `2016` est   mun  
##    <chr>            <dbl> <chr>  <chr> <chr>
##  1 analfabeta        2.59 <NA>   01    001  
##  2 poblacion    870274    881300 01    001  
##  3 sin_primaria     11.5  <NA>   01    001  
##  4 analfabeta        5.54 <NA>   01    002  
##  5 poblacion     49608    50260  01    002  
##  6 sin_primaria     26.0  <NA>   01    002  
##  7 analfabeta        6.19 <NA>   01    003  
##  8 poblacion     57951    58585  01    003  
##  9 sin_primaria     28.9  <NA>   01    003  
## 10 analfabeta        4.35 <NA>   01    004  
## # ... with 7,359 more rows

gather() y spread()

df_gather <- df_fo %>% 
  gather(key = year, 
         value = valor, 
         c(`2015`, `2016`))
df_gather
## # A tibble: 14,738 x 5
##    indicador    est   mun   year  valor 
##    <chr>        <chr> <chr> <chr> <chr> 
##  1 analfabeta   01    001   2015  2.59  
##  2 poblacion    01    001   2015  870274
##  3 sin_primaria 01    001   2015  11.48 
##  4 analfabeta   01    002   2015  5.54  
##  5 poblacion    01    002   2015  49608 
##  6 sin_primaria 01    002   2015  26.02 
##  7 analfabeta   01    003   2015  6.19  
##  8 poblacion    01    003   2015  57951 
##  9 sin_primaria 01    003   2015  28.88 
## 10 analfabeta   01    004   2015  4.35  
## # ... with 14,728 more rows

gather() y spread()

df_tidy <- df_gather %>% 
  spread(key = indicador, 
         value = valor) 
df_tidy
## # A tibble: 4,914 x 6
##    est   mun   year  analfabeta poblacion sin_primaria
##    <chr> <chr> <chr> <chr>      <chr>     <chr>       
##  1 01    001   2015  2.59       870274    11.48       
##  2 01    001   2016  <NA>       881300    <NA>        
##  3 01    002   2015  5.54       49608     26.02       
##  4 01    002   2016  <NA>       50260     <NA>        
##  5 01    003   2015  6.19       57951     28.88       
##  6 01    003   2016  <NA>       58585     <NA>        
##  7 01    004   2015  4.35       16274     21.81       
##  8 01    004   2016  <NA>       16483     <NA>        
##  9 01    005   2015  3.76       112342    18.21       
## 10 01    005   2016  <NA>       114130    <NA>        
## # ... with 4,904 more rows

Explorar

Mirar los datos, generar hipótesis rápidamente, probarlas rápidamente y luego repetirlas una y otra vez. Generar muchas pistas.

Separar, aplicar y combinar

group_by(), mutate() y summarise()

df_resumen <- df_tidy  %>% 
  # ordenar para obtener diferencia poblacional
  arrange(est, mun, year) %>% 
  # tidyverse verbs
  mutate_at(.vars = c('analfabeta', 'poblacion', 'sin_primaria'), 
            .funs = parse_number) %>% 
  # agrupar por estado y municipio
  group_by(est, mun) %>% 
  # diferencia de poblacion de 2015 a 2016
  mutate(analfabeta = mean(analfabeta, na.rm = T), 
         sin_primaria = mean(sin_primaria, na.rm = T),
         dif_poblacion = diff(poblacion), 
         difp_poblacion = dif_poblacion/poblacion) %>% 
  # desagrupar
  ungroup %>% 
  # únicamente cambio porcentual de 2016
  filter(year == '2016')  

EJ: separar, aplicar y combinar

df_resumen
## # A tibble: 2,457 x 8
##    est   mun   year  analfabeta poblacion sin_primaria dif_poblacion
##    <chr> <chr> <chr>      <dbl>     <dbl>        <dbl>         <dbl>
##  1 01    001   2016        2.59    881300         11.5         11026
##  2 01    002   2016        5.54     50260         26.0           652
##  3 01    003   2016        6.19     58585         28.9           634
##  4 01    004   2016        4.35     16483         21.8           209
##  5 01    005   2016        3.76    114130         18.2          1788
##  6 01    006   2016        4.11     45463         18.7           555
##  7 01    007   2016        4.70     53560         19.2           665
##  8 01    008   2016        3.45      9320         18.9           124
##  9 01    009   2016        5.91     21843         26.8           291
## 10 01    010   2016        5.77     20626         25.7           254
## # ... with 2,447 more rows, and 1 more variable: difp_poblacion <dbl>

Visualizar

"Una gráfica simple ha traído más información a la mente del analista de datos que cualquier otro recurso." - John Tukey

ggplot2

Es una sistema para crear gráficos basado en la gramática de gráficos con los mismos elementos.

Se basa en el trabajo de Wilkinson y propone la gramática en capas o layered grammar of graphics adaptado a R.

La gramática nos dice que un gráfico estadístico es un mapeo de datos a atributos estéticos de objetos geométricos en sistemas de coordenadas específicos.

Componentes

Existen tres componentes básicos en ggplot2:

  1. data: Dataframe de datos a graficar.
  2. aes aesthetic mappings: Mapeo de las variables del conjunto de datos y la propiedades visuales a valores estéticos de la gráfica.
  3. geoms: Al menos una capa con objetos geométricos que describan como tratar cada observación.

EJ: analfabeta vs sin primaria

df_resumen %>% 
  filter(est %in% c("07", "09", "28", "30")) %>% 
  ggplot(aes(x = analfabeta, y = sin_primaria, 
             color = est)) + 
  geom_point(alpha = .5, size = 2)

EJ: analfabeta vs crecimiento poblacional

df_resumen %>% 
  filter(est %in% c("07", "09", "28", "30")) %>% 
  ggplot(aes(x = analfabeta, y = difp_poblacion, 
             color = est)) + 
  geom_point(alpha = .5, size = 2)

EJ: Algunas modificaciones

df_resumen %>% 
  filter(est %in% c("07", "09", "28", "30")) %>% 
  # recodificación de factores
  mutate(est = forcats::fct_recode(est, 
                          chiapas = '07',
                          cdmx = '09',
                          tamaulipas = '28',
                          veracruz = '30')) %>% 
  ggplot(aes(x = sin_primaria, y = difp_poblacion, 
             color = est)) + 
  geom_point(alpha = .5, size = 2) + 
  # etiquetas
  ylab("Diferencia porcentual\npoblación") + 
  xlab("Porc. sin primaria") + 
  ggtitle("Relación de variables.", 
          "Información por municipio.") + 
  guides(color = guide_legend(title = "Entidad"))

EJ: Grafica con modificaciones

Modelar

Regresión lineal

mod_1 <- lm(formula = difp_poblacion ~ sin_primaria, data = df_resumen)
summary(mod_1)
## 
## Call:
## lm(formula = difp_poblacion ~ sin_primaria, data = df_resumen)
## 
## Residuals:
##        Min         1Q     Median         3Q        Max 
## -0.0210081 -0.0028225 -0.0002315  0.0027997  0.0227699 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   1.355e-02  3.016e-04   44.94   <2e-16 ***
## sin_primaria -1.397e-04  8.814e-06  -15.85   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.004688 on 1891 degrees of freedom
##   (564 observations deleted due to missingness)
## Multiple R-squared:  0.1172, Adjusted R-squared:  0.1168 
## F-statistic: 251.2 on 1 and 1891 DF,  p-value: < 2.2e-16

broom

Convierte en data frames ordenados la salida desordenada de las funciones incorporadas en R, como lm, nls o t.test.

library(broom)
tidy(mod_1)
##           term     estimate    std.error statistic       p.value
## 1  (Intercept)  0.013554178 3.015929e-04  44.94197 1.080268e-300
## 2 sin_primaria -0.000139689 8.814329e-06 -15.84794  3.305014e-53

modelr

Proporciona funciones que ayudan a crear pipelines elegantes al modelar.

library(modelr)
tab_results <- df_resumen %>% 
  # agrega una columna de predicciones
  modelr::add_predictions(model = mod_1) %>% 
  # agrega una columna de residuales
  modelr::add_residuals(model = mod_1)

EJ: Resultados

##   est mun year analfabeta poblacion sin_primaria dif_poblacion
## 1  01 001 2016       2.59    881300        11.48         11026
## 2  01 002 2016       5.54     50260        26.02           652
## 3  01 003 2016       6.19     58585        28.88           634
## 4  01 004 2016       4.35     16483        21.81           209
## 5  01 005 2016       3.76    114130        18.21          1788
## 6  01 006 2016       4.11     45463        18.74           555
##   difp_poblacion        pred        resid
## 1     0.01251106 0.011950549 0.0005605146
## 2     0.01297254 0.009919471 0.0030530718
## 3     0.01082188 0.009519961 0.0013019222
## 4     0.01267973 0.010507562 0.0021721691
## 5     0.01566635 0.011010442 0.0046559036
## 6     0.01220773 0.010936407 0.0012713227

EJ: Función do()

# Un poco mas complicado
modelo_fun <- function(sub_tab){
  mod <- lm(formula = difp_poblacion ~ sin_primaria, data = sub_tab)
  broom::tidy(mod)
}

tbl_modelos <- df_resumen %>% 
  # divido en subgrupos por estado
  group_by(est) %>% 
  # aplicar la función para cada subgrupo
  do(modelo_fun(sub_tab = .))

EJ: Tabla

## # A tibble: 8 x 6
## # Groups:   est [4]
##   est   term           estimate std.error statistic  p.value
##   <chr> <chr>             <dbl>     <dbl>     <dbl>    <dbl>
## 1 01    (Intercept)   0.0151     0.00236      6.39  0.000127
## 2 01    sin_primaria -0.0000887  0.000107    -0.826 0.430   
## 3 02    (Intercept)   0.0112     0.00578      1.94  0.147   
## 4 02    sin_primaria  0.000231   0.000396     0.583 0.601   
## 5 03    (Intercept)   0.0358     0.00754      4.74  0.0178  
## 6 03    sin_primaria -0.000482   0.000419    -1.15  0.333   
## 7 04    (Intercept)   0.0134     0.00301      4.47  0.00156 
## 8 04    sin_primaria  0.0000171  0.000104     0.165 0.873

EJ: Grafica de coeficientes

library(ggrepel)
tbl_modelos %>% select(est:estimate) %>% 
  spread(term, estimate) %>% 
  ggplot(aes(x = `(Intercept)`, y = sin_primaria)) + 
  geom_text_repel(aes( label = est)) +
  geom_point()

EJ: Mas información

modelo_fun2 <- function(sub_tab){
  mod <- lm(formula = difp_poblacion ~ sin_primaria, data = sub_tab)
}

tbl_modelos_2 <- df_resumen %>% 
  group_by(est) %>% 
  do(modelo = modelo_fun2(sub_tab = .), 
     #guardar el modelo
     coefs = broom::tidy(x = modelo_fun2(sub_tab = .)), 
     # coeficientes en dataframe
     rmse = modelr::rmse(model = modelo_fun2(sub_tab = .),
                         data = .) )
     # calcula rmse

EJ: Tabla

tbl_modelos_2 %>% head 
## # A tibble: 6 x 4
##   est   modelo   coefs                rmse     
##   <chr> <list>   <list>               <list>   
## 1 01    <S3: lm> <data.frame [2 × 5]> <dbl [1]>
## 2 02    <S3: lm> <data.frame [2 × 5]> <dbl [1]>
## 3 03    <S3: lm> <data.frame [2 × 5]> <dbl [1]>
## 4 04    <S3: lm> <data.frame [2 × 5]> <dbl [1]>
## 5 05    <S3: lm> <data.frame [2 × 5]> <dbl [1]>
## 6 06    <S3: lm> <data.frame [2 × 5]> <dbl [1]>

Libro GRATIS

Más paquetes de tidyverse

Algunas referencias:

¡Gracias!